package com.zyf.chapter02.creation.factory.DI;

import com.zyf.chapter02.creation.factory.DI.util.BeanCreationFailureException;
import com.zyf.chapter02.creation.factory.DI.util.NoSuchBeanDefinitionException;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * bean 工厂
 * 为 DI 容器提供实例
 * @author yifeng
 */
public class BeansFactory {
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();

    public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) throws NoSuchBeanDefinitionException {
        for (BeanDefinition beanDefinition : beanDefinitionList) {
            this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);
        }

        for (BeanDefinition beanDefinition : beanDefinitionList) {
            // 如果是不是懒加载且是单例，则直接创建
            if (!beanDefinition.isLazyInit() && beanDefinition.isSingleton()) {
                try {
                    createBean(beanDefinition);
                }  catch (BeanCreationFailureException e) {
                    throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanDefinition.getId());
                }
            }
        }
    }

    public Object getBean(String beanId) throws NoSuchBeanDefinitionException {
        BeanDefinition beanDefinition = beanDefinitions.get(beanId);
        if (beanDefinition == null) {
            throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);
        }
        try {
            return createBean(beanDefinition);
        } catch (BeanCreationFailureException e) {
            throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);
        }
    }

    private Object createBean(BeanDefinition beanDefinition) throws BeanCreationFailureException {
        if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {
            return singletonObjects.get(beanDefinition.getId());
        }

        Object bean = null;
        try {
            Class beanClass = Class.forName(beanDefinition.getClassName());
            List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();
            // 无构造参数则直接创建实例
            if (args.isEmpty()) {
                bean = beanClass.newInstance();
            } else {
                Class[] argClasses = new Class[args.size()];
                Object[] argObjects = new Object[args.size()];
                for (int i = 0; i < args.size(); i++) {
                    BeanDefinition.ConstructorArg arg = args.get(i);
                    // 如果参数无引用
                    if (!arg.isRef()) {
                         argClasses[i] = arg.getType();
                         argObjects[i] = arg.getArg();
                    } else {
                        // 获取构造参数的beanDefinition
                        BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());
                        if (refBeanDefinition == null) {
                            throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg());
                        }
                        argClasses[i] = Class.forName(refBeanDefinition.getClassName());
                        // 递归调用，可能存在循环依赖的问题
                        argObjects[i] = createBean(refBeanDefinition);
                    }
                }
                // 使用反射创建实例
                bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
            }
        } catch (ClassNotFoundException | IllegalAccessException
                | InstantiationException | NoSuchMethodException
                | NoSuchBeanDefinitionException | InvocationTargetException e) {
            // 封装多个异常为一个
            throw new BeanCreationFailureException("", e);
        }
        if (bean != null && beanDefinition.isSingleton()) {
            singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
            return singletonObjects.get(beanDefinition.getId());
        }
        return bean;
    }

}
